#!/bin/sh
# Script for configuring wireless adapters with connection problems
# Solve troubles with some adapters such as Realtek RTL8188CE WLAN
# It can be used independently or as dispatcher of NetworkManager
#
# Copyright (C) 2014 Alex Bilinsky <alex dot bilinsky at gmail dot com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>

# Usage:
#     %% INTERFACE [OPTIONS...]
#
# Options:
#     auto            configure with auto settings detection
#     bound[=]VALUE   signal quality bound: min 5 (default: 9)
#     level[=]VALUE   signal quality level: 0 - 9 (default: 5)
#     rate[=]VALUE    maximum data transfer rate:
#                     54M 48M 36M 24M 18M 12M 11M 9M 6M 5.5M 2M 1M
#     power[=]VALUE   maximum transmit power: max 33 (modified driver)
#     rts[=]VALUE     RTS frame size threshold: min 500 (default: 2347)
#     data[=]VALUE    data fragment threshold: min 2048 (default: off)
#     retry[=]VALUE   retries number limit: 1-30 (default: 7)
#     help, --help    print this help
#
# Dispatcher:
#     /etc/NetworkManager/dispatcher.d/%%
#
# Permissions:
#     chown root:root %%
#     chmod 755 %%
#

# Signal quality bound
# minimum 5 (default: 9)
QUALITY_BOUND=
# QUALITY_BOUND=5 # (default level: 3)
# Level Rate Power Frame Packet Retry Sens
#   0   1M    33   500   2048    30   -90
#   1   6M    31   850   4096    26   -72
#   2   12M   28   1200  8192    21   -55
#   3   24M   26   1550  16384   17   -37
#   4   36M   23   1900  32768   12   -20
#   5   54M   20   2347  off     7    -3
# QUALITY_BOUND=9 # (default level: 5)
# Level Rate Power Frame Packet Retry Sens
#   0   1M    33   500   2048    30   -90
#   1   2M    32   700   2048    28   -80
#   2   5.5M  21   900   4096    25   -70
#   3   9M    29   1100  8192    25   -61
#   4   12M   28   1300  8192    20   -51
#   5   18M   26   1500  16384   18   -42
#   6   24M   25   1700  32768   15   -32
#   7   36M   23   1900  32768   13   -23
#   8   48M   22   2100  65536   10   -13
#   9   54M   20   2347  off     7    -3

# Signal quality level
# bound 5: 0 - 5 (default: 3)
# bound 9: 0 - 9 (default: 5)
QUALITY_LEVEL=

# Maximum data transfer rate
# 54M 48M 36M 24M 18M 12M 11M 9M 6M 5.5M 2M 1M
TRANSFER_RATE=

# RTS frame size threshold
# minimum 500 (default: 2347)
RTS_THRESHOLD=

# Data fragment size threshold
# minimum 2048 (default: off)
DATA_FRAGMENT=

# Retries number limit
# 1 - 30 (default: 7)
RETRIES_LIMIT=


# Begin configuring
THIS="${0##*/}"
# Determine wireless adapters
WIRELESS_CARDS=`/sbin/lspci -k | sed '/\(wireless\|wifi\|802\.11\)/I,/Kernel driver/!d'`
ADAPTER_MODULE=`echo "$WIRELESS_CARDS" | sed '/Kernel driver/!d;s/.*Kernel driver in use: \(.\+\)$/\1/'`
ADAPTER_NUMBER=`echo "$WIRELESS_CARDS" | sed '/^\t/d' | cut -d' ' -f1 | cut -d. -f2`
ADAPTER_MODULE=( $ADAPTER_MODULE )
ADAPTER_NUMBER=( $ADAPTER_NUMBER )
# Determine interfaces
INTERFACE_LIST=`/sbin/iwconfig 2>&1 | grep 'IEEE 802.11' | cut -d' ' -f1`
if [ -z "$INTERFACE_LIST" ]; then
	echo "$THIS: wireless network interface not found"
	logger -t "$THIS" "wireless network interface not found"
	exit
fi
# Parse options
for option; do
	count=$(( $count + 1 ))
	case $option in
		auto)                     WIRELESS_AUTO=true ;;
		[0-9]*)                   QUALITY_LEVEL=$option ;;
		level=[0-9]*|level[0-9]*) QUALITY_LEVEL=${option##*[a-z=]} ;;
		bound=[0-9]*|bound[0-9]*) QUALITY_BOUND=${option##*[a-z=]} ;;
		rate=[0-9]*|rate[0-9]*)   TRANSFER_RATE=${option##*[a-z=]} ;;
		power=[0-9]*|power[0-9]*) TRANSMIT_POWER=${option##*[a-z=]} ;;
		rts=[0-9]*|rts[0-9]*)     RTS_THRESHOLD=${option##*[a-z=]} ;;
		data=[0-9]*|data[0-9]*)   DATA_FRAGMENT=${option##*[a-z=]} ;;
		retry=[0-9]*|retry[0-9]*) RETRIES_LIMIT=${option##*[a-z=]} ;;
		*help)
			sed "1,/^$/d;/^$/,$ d;s/# \?//;s/%%/$THIS/" "$0"
			exit
			;;
		*)
			case $count in
				1)
					if echo "$INTERFACE_LIST" | grep -q "$option"; then
						INTERFACE_LIST=$option
					else
						unset INTERFACE_LIST
						break
					fi
					;;
				2)
					case $option in
						up)
							DISPATCHER_MODE=true
							continue
							;;
						down|vpn-up|vpn-down|hostname|dhcp4-change|dhcp6-change)
							logger -t "$THIS" "unsupported action '$option'"
							exit
							;;
						*)
							WRONG_OPTIONS=true
							break
					esac
					;;
				*)
					WRONG_OPTIONS=true
					break
			esac
	esac
done
# Incorrect interface
if [ -z "$INTERFACE_LIST" ]; then
	echo "$THIS: incorrect interface '$option'"
	logger -t "$THIS" "incorrect interface '$option'"
	exit
fi
# Invalid option
if [ $WRONG_OPTIONS ]; then
	echo "$THIS: uncertain option '$option'"
	logger -t "$THIS" "uncertain option '$option'"
	exit
fi
# Print table header
echo 'Wireless   Quality  Transfer  Transmit  RTS        Data      Retries  Sensi-'
echo 'interface  level    rate      power     threshold  fragment  limit    tivity'
logger -t "$THIS" 'Wireless   Quality  Transfer  Transmit  RTS        Data      Retries  Sensi-'
logger -t "$THIS" 'interface  level    rate      power     threshold  fragment  limit    tivity'
# Customize found interfaces
for wlan in $INTERFACE_LIST; do
	# Determine kernel module name
	for (( i=0 ; i < ${#ADAPTER_NUMBER[@]} ; i++ )); do
		if [ ${wlan##*[a-z]} -eq ${ADAPTER_NUMBER[$i]} ]; then
			ADAPTER_MODULE=${ADAPTER_MODULE[$i]}
			break
		fi
	done
	# Initial information
	INTERFACE_NAME=$wlan
	INTERFACE_INFO=`/sbin/iwconfig $INTERFACE_NAME`
	MODULE_DETAILS=`/sbin/modinfo $ADAPTER_MODULE`
	# Initial settings
	INITIAL_RATE=`expr match "$INTERFACE_INFO" '.*Bit Rate=\([0-9]\+ [kMG]\)'`
	INITIAL_RATE=${INITIAL_RATE/ /}
	INITIAL_POWER=`expr match "$INTERFACE_INFO" '.*Tx-Power=\([0-9]\+\)'`
	INITIAL_LEVEL=`expr match "$INTERFACE_INFO" '.*Link Quality=\([0-9/]\+\)'`
	# Auto detect settings
	if [ $WIRELESS_AUTO ]; then
		unset QUALITY_BOUND QUALITY_LEVEL TRANSFER_RATE TRANSMIT_POWER RTS_THRESHOLD DATA_FRAGMENT RETRIES_LIMIT
	fi
	# Default quality bound
	QUALITY_BOUND=${QUALITY_BOUND:-9}
	# Specific settings
	case $ADAPTER_MODULE in
		rtl8192ce)
			# Proprietary Realtek RTL8188CE WLAN driver
			if echo "$MODULE_DETAILS" | grep -q 'Benjamin Porter'; then
				if [ -z "$INITIAL_RATE" ] && [ -z "$INITIAL_LEVEL" ] || [ $DISPATCHER_MODE ]; then
					QUALITY_LEVEL=${QUALITY_LEVEL:-$QUALITY_BOUND}
					TRANSMIT_POWER=${TRANSMIT_POWER:-33}
				else
					QUALITY_SHIFT=2
					POWER_MAXIMUM=33
					POWER_DEFAULT=20
				fi
			fi
	esac
	QUALITY_SHIFT=${QUALITY_SHIFT:-1}
	POWER_MAXIMUM=${POWER_MAXIMUM:-$INITIAL_POWER}
	POWER_DEFAULT=${POWER_DEFAULT:-$INITIAL_POWER}
	# Quality level
	if [ "$INITIAL_LEVEL" ]; then
		QUALITY_LEVEL=${QUALITY_LEVEL:-$(( ${INITIAL_LEVEL%/*} * $QUALITY_BOUND / ${INITIAL_LEVEL#*/} ))}
	else
		QUALITY_LEVEL=${QUALITY_LEVEL:-$(( $QUALITY_BOUND / 2 + $QUALITY_SHIFT ))}
	fi
	# Bit-rates values
	case $QUALITY_BOUND in
		5) DEGREE_BOUND=5  BITRATE_ARRAY=( 1M 6M 12M 24M 36M 54M ) ;;
		9) DEGREE_BOUND=6  BITRATE_ARRAY=( 1M 2M 5.5M 9M 12M 18M 24M 36M 48M 54M ) ;;
		*) DEGREE_BOUND=6  BITRATE_ARRAY=( 1M 2M 5.5M 6M 9M 11M 12M 18M 24M 36M 48M 54M )
	esac
	# Data transfer rate
	BITRATE_BOUND=$(( ${#BITRATE_ARRAY[@]} - 1 ))
	BITRATE_INDEX=$(( $BITRATE_BOUND * $QUALITY_LEVEL / $QUALITY_BOUND ))
	TRANSFER_RATE=${TRANSFER_RATE:-${BITRATE_ARRAY[$BITRATE_INDEX]}}
	if test "$TRANSFER_RATE" && ! echo "${BITRATE_ARRAY[@]}" | grep -qw "$TRANSFER_RATE"; then
		unset TRANSFER_RATE
	fi
	if test "$INITIAL_RATE" && ! echo "${BITRATE_ARRAY[@]}" | grep -qw "$INITIAL_RATE"; then
		unset TRANSFER_RATE
	fi
	# Signal transmit power
	TRANSMIT_POWER=${TRANSMIT_POWER:-$(( $POWER_MAXIMUM - $(( $POWER_MAXIMUM - $POWER_DEFAULT )) * $QUALITY_LEVEL / $QUALITY_BOUND ))}
	# Packet fragmentation threshold
	if [ $QUALITY_LEVEL -ne $QUALITY_BOUND ]; then
		RTS_THRESHOLD=${RTS_THRESHOLD:-$(( 500 + 36 * $QUALITY_LEVEL / $QUALITY_BOUND * 50 ))}
		DATA_FRAGMENT=${DATA_FRAGMENT:-$(( 2048 * 2 ** $(( $DEGREE_BOUND * $QUALITY_LEVEL / $QUALITY_BOUND )) ))}
	else
		RTS_THRESHOLD=${RTS_THRESHOLD:-2347}
		DATA_FRAGMENT=${DATA_FRAGMENT:-off}
	fi
	# Maximum retries number
	RETRIES_LIMIT=${RETRIES_LIMIT:-$(( 30 - 23 * $QUALITY_LEVEL / $QUALITY_BOUND ))}
	# Sensitivity threshold
	SENSITIVITY=${SENSITIVITY:-$(( $TRANSMIT_POWER + 100 * $QUALITY_LEVEL / $QUALITY_BOUND - 123 ))}
	# Apply settings
	if ! echo "$INTERFACE_INFO" | grep -q 'Power Management:off'; then
		/sbin/iwconfig $INTERFACE_NAME power off
	fi
	if echo "$INTERFACE_INFO" | grep -q 'Bit Rate' && test "$TRANSFER_RATE"; then
		/sbin/iwconfig $INTERFACE_NAME rate fixed
		/sbin/iwconfig $INTERFACE_NAME rate $TRANSFER_RATE 2>/dev/null || TRANSFER_RATE='wrong!'
		/sbin/iwconfig $INTERFACE_NAME bit fixed
		/sbin/iwconfig $INTERFACE_NAME bit $TRANSFER_RATE 2>/dev/null || TRANSFER_RATE='wrong!'
	else
		[ "$INITIAL_RATE" ] && TRANSFER_RATE=$INITIAL_RATE || TRANSFER_RATE='--'
	fi
	if echo "$INTERFACE_INFO" | grep -q 'Tx-Power'; then
		/sbin/iwconfig $INTERFACE_NAME txpower $TRANSMIT_POWER 2>/dev/null || TRANSMIT_POWER='wrong!'
	fi
	if echo "$INTERFACE_INFO" | grep -q 'Retry short.*limit'; then
		/sbin/iwconfig $INTERFACE_NAME retry $RETRIES_LIMIT 2>/dev/null || RETRIES_LIMIT='wrong!'
	fi
	if echo "$INTERFACE_INFO" | grep -q 'RTS thr'; then
		/sbin/iwconfig $INTERFACE_NAME rts $RTS_THRESHOLD 2>/dev/null || RTS_THRESHOLD='wrong!'
	fi
	if echo "$INTERFACE_INFO" | grep -q 'Fragment thr'; then
		/sbin/iwconfig $INTERFACE_NAME frag $DATA_FRAGMENT 2>/dev/null || DATA_FRAGMENT='wrong!'
	fi
	if echo "$INTERFACE_INFO" | grep -q 'Sensitivity'; then
		/sbin/iwconfig $INTERFACE_NAME sens $SENSITIVITY 2>/dev/null || SENSITIVITY='wrong!'
	else
		SENSITIVITY='--'
	fi
	# Print settings
	OUTPUT_VALUES=`printf '%-11s %-8s %-9s %-9s %-10s %-9s %-8s %s' "$INTERFACE_NAME" \
			"$QUALITY_LEVEL/$QUALITY_BOUND" "$TRANSFER_RATE" "$TRANSMIT_POWER" \
			"$RTS_THRESHOLD" "$DATA_FRAGMENT" "$RETRIES_LIMIT" "$SENSITIVITY"`
	echo "$OUTPUT_VALUES"
	logger -t "$THIS" "$OUTPUT_VALUES"
done
# Print interface status
for wlan in $INTERFACE_LIST; do
	echo
	/sbin/iwconfig $wlan
done
